package me.ahoukuse.koala.service.impl;

import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import me.ahoukuse.koala.config.KoalaConfigure;
import me.ahoukuse.koala.dao.FileEntityDao;
import me.ahoukuse.koala.po.FileEntity;
import me.ahoukuse.koala.service.FileService;
import me.ahoukuse.koala.util.FileUtil;
import me.ahoukuse.koala.util.JwtUtil;
import me.ahoukuse.koala.util.MessageUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.security.NoSuchAlgorithmException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.UUID;

@Service
@Slf4j
public class FileServiceImpl implements FileService {

    @Autowired
    FileEntityDao fileEntityDao;


    @Autowired
    KoalaConfigure koalaConfigure;

    private final String split = File.separator;

    private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    private final Object myLock = new Object();
    @Override
    public JSONObject moveFilesTo(List<String> ids, int destFolderId) {
        JSONObject res = null;
        if (!ids.contains(String.valueOf(destFolderId))) {
            fileEntityDao.moveFilesTo(ids, destFolderId);
            res = MessageUtil.msg(200, "success");
        } else {
            res = MessageUtil.msg(602, "error");
        }
        return res;
    }

    @Override
    public JSONObject copyFilesTo(List<String> ids, int destFolderId) {
        JSONObject res = null;
        if (!ids.contains(String.valueOf(destFolderId))) {
            List<FileEntity> fileEntities = fileEntityDao.getFileByIds(ids);
            fileEntities.forEach((fileEntity -> {
                fileEntity.setParentId(destFolderId);
            }));
            fileEntityDao.insertBatch(fileEntities);
            res = MessageUtil.msg(200, "success");
        } else {
            res = MessageUtil.msg(601, "error");
        }
        return res;
    }

    @Override
    public JSONObject initChunkUpload(String fileName, String fileHash, int numChunk, int parentId) {
        int userId = JwtUtil.getUserIdFromJwt();
        LocalDateTime now = LocalDateTime.now();
        String timePrefix = now.format(formatter);
        String chunkId = fileHash;
        String strChunkFolder = koalaConfigure.getUploadedFileLocation() + split + chunkId;
        File chunkFolder = new File(strChunkFolder);
        if (!chunkFolder.exists()) {
            if (!chunkFolder.mkdir()) {
                return MessageUtil.msg(500, "error while create folder.");
            }
        }
        String strDotConf = strChunkFolder + split + ".conf";
        File  dotConf = new File(strDotConf);
        if (!dotConf.exists()) {
            try (FileOutputStream fos = new FileOutputStream(dotConf);
                 DataOutputStream dos = new DataOutputStream(fos)) {
                dos.writeInt(numChunk);
                dos.writeInt(0);
                byte[] tempByte = new byte[numChunk];
                dos.write(tempByte);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        String strFile = strChunkFolder + split + fileName;
        File file = new File(strFile);
        if (!file.exists()) {
            try {
                file.createNewFile();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        JSONObject res = MessageUtil.msg(200, "success");
        JSONObject chunkSession = new JSONObject();
        chunkSession.put("status", 0);
        chunkSession.put("chunkId", chunkId);
        res.put("data",chunkSession);
        return res;
    }

    @Override
    public JSONObject chunkUpload(String chunkId, String fileName, int chunkIdx, int chunkSize, MultipartFile chunk, int parentId) {
        String strFile = koalaConfigure.getUploadedFileLocation() + split + chunkId + split + fileName;
        boolean uploadFinished = false;
        File file = new File(strFile);
        try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
            byte[] chunkBytes = chunk.getBytes();
            long pos = (long) chunkIdx * chunkSize;
            raf.seek(pos);
            raf.write(chunkBytes);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        String strDotConf = koalaConfigure.getUploadedFileLocation() + split + chunkId + split + ".conf" ;
        File dotConfFile = new File(strDotConf);
        try (RandomAccessFile raf = new RandomAccessFile(dotConfFile, "rw");) {
            int totalChunk = raf.readInt();
            int totalUploadedChunk = 0;
            synchronized (myLock) {
                totalUploadedChunk = raf.readInt();
                totalUploadedChunk += 1;
                raf.seek(4);
                raf.writeInt(totalUploadedChunk);
            }
            log.info("curr totalUploadedChunk = {}",totalUploadedChunk);
            raf.seek(chunkIdx + 8);
            raf.write(Byte.MAX_VALUE);

            if (totalUploadedChunk == totalChunk) {
                uploadFinished = true;
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }


        if (uploadFinished) {
           return finishChunkUpload(chunkId, fileName, parentId);
        }

        return MessageUtil.msg(200, "success");
    }

    @Override
    public JSONObject checkChunk(String chunkId, String fileName, int chunkIdx, int chunkSize) {
        String strDotConf = koalaConfigure.getUploadedFileLocation() + split + chunkId + split + ".conf" ;
        File dotConfFile = new File(strDotConf);
        boolean uploaded = false;
        try (RandomAccessFile raf = new RandomAccessFile(dotConfFile, "r")) {
            raf.seek(chunkIdx + 8);
            byte uploadFlag =  raf.readByte();
            uploaded = uploadFlag == Byte.MAX_VALUE;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        JSONObject res = new JSONObject();
        JSONObject obj = new JSONObject();
        obj.put("uploaded", uploaded);
        res.put("data", obj);
        return res;
    }

    @Override
    public JSONObject finishChunkUpload(String chunkId, String fileName, int parentId) {
        int userId = JwtUtil.getUserIdFromJwt();
        String sourcePath = koalaConfigure.getUploadedFileLocation() + split + chunkId + split + fileName;
        String extName = FileUtil.getFileExtension(fileName);
        String uuid = UUID.randomUUID().toString();
        String prefix = koalaConfigure.getUploadedFileLocation() + split;
        String targetPath = prefix+uuid+"."+extName;
        JSONObject res =  MessageUtil.msg(500, "error");
        try {
            FileUtil.moveFile(sourcePath, targetPath);
            String strSourceDotConf = koalaConfigure.getUploadedFileLocation() + split + chunkId + split+".conf";
            File sourceDotConf = new File(strSourceDotConf);
            sourceDotConf.delete();
            String strSourceFolder = koalaConfigure.getUploadedFileLocation() + split + chunkId;
            File sourceFolder = new File(strSourceFolder);
            sourceFolder.delete();
            long fileSize = FileUtil.getFileSize(targetPath);
            String md5 = FileUtil.calculateMD5(targetPath);
            if (md5.equalsIgnoreCase(chunkId)) {
                FileEntity entity = new FileEntity();
                entity.setFileName(fileName);
                entity.setFilePath(uuid+"."+extName);
                entity.setType(0);
                entity.setFileType(FileUtil.getFileType(fileName));
                entity.setOwnerId(userId);
                entity.setParentId(parentId);
                entity.setFileSize(fileSize); // getSize返回的单位是字节
                entity.setFileHash(md5);
                entity.setCreateTime(new Date());
                fileEntityDao.insertOne(entity);
                res = MessageUtil.msg(200, "success");
                log.info("md5  match !!!");
            } else {
                log.warn("md5 don't match");
                res = MessageUtil.msg(500, "md5 don't match");
            }

        } catch (IOException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return res;
    }
}

